home *** CD-ROM | disk | FTP | other *** search
/ Sun Solutions 2000 #2 / Sun Solutions CD (Volume 2 2000)(Special Focus - Java Technologies)(Disc 1).ISO / products / SunSolutions / classes / imagemap.java < prev    next >
Text File  |  2000-01-05  |  32KB  |  1,179 lines

  1. import java.applet.Applet;
  2. import java.awt.Image;
  3. import java.awt.Graphics;
  4. import java.awt.Rectangle;
  5. import java.util.StringTokenizer;
  6. import java.util.Vector;
  7. import java.util.Hashtable;
  8. import java.net.URL;
  9. import java.awt.image.*;
  10. import java.net.MalformedURLException;
  11.  
  12. /**
  13.  * An extensible ImageMap applet class.
  14.  * The active areas on the image are controlled by ImageArea classes
  15.  * that can be dynamically loaded over the net.
  16.  *
  17.  * @author     Jim Graham
  18.  * @version     %I%, %G%
  19.  */
  20. public class ImageMap extends Applet {
  21.     /**
  22.      * The unhighlighted image being mapped.
  23.      */
  24.     Image baseImage;
  25.  
  26.     /**
  27.      * The list of image area handling objects;
  28.      */
  29.     ImageMapArea areas[];
  30.  
  31.     /**
  32.      * The primary highlight mode to be used.
  33.      */
  34.     static final int BRIGHTER = 0;
  35.     static final int DARKER = 1;
  36.  
  37.     int hlmode = BRIGHTER;
  38.  
  39.     /**
  40.      * The percentage of highlight to apply for the primary highlight mode.
  41.      */
  42.     int hlpercent = 50;
  43.  
  44.     /**
  45.      * Get a rectangular region of the baseImage highlighted according to
  46.      * the primary highlight specification.
  47.      */
  48.     Image getHighlight(int x, int y, int w, int h) {
  49.     return getHighlight(x, y, w, h, hlmode, hlpercent);
  50.     }
  51.  
  52.     /**
  53.      * Get a rectangular region of the baseImage with a specific highlight.
  54.      */
  55.     Image getHighlight(int x, int y, int w, int h, int mode, int percent) {
  56.     return getHighlight(x, y, w, h, new HighlightFilter(mode == BRIGHTER,
  57.                                 percent));
  58.     }
  59.  
  60.     /**
  61.      * Get a rectangular region of the baseImage modified by an image filter.
  62.      */
  63.     Image getHighlight(int x, int y, int w, int h, ImageFilter filter) {
  64.     Image cropped = makeImage(baseImage, new CropImageFilter(x, y, w, h));
  65.     return makeImage(cropped, filter);
  66.     }
  67.  
  68.     /**
  69.      * Make the primary highlighted version of the baseImage.
  70.      */
  71.     Image makeImage(Image orig, ImageFilter filter) {
  72.     return createImage(new FilteredImageSource(orig.getSource(), filter));
  73.     }
  74.  
  75.     /**
  76.      * Parse a string representing the desired highlight to be applied.
  77.      */
  78.     void parseHighlight(String s) {
  79.     if (s == null) {
  80.         return;
  81.     }
  82.     if (s.startsWith("brighter")) {
  83.         hlmode = BRIGHTER;
  84.         if (s.length() > "brighter".length()) {
  85.         hlpercent = Integer.parseInt(s.substring("brighter".length()));
  86.         }
  87.     } else if (s.startsWith("darker")) {
  88.         hlmode = DARKER;
  89.         if (s.length() > "darker".length()) {
  90.         hlpercent = Integer.parseInt(s.substring("darker".length()));
  91.         }
  92.     }
  93.     }
  94.  
  95.     /**
  96.      * Initialize the applet. Get attributes.
  97.      *
  98.      * Initialize the ImageAreas.
  99.      * Each ImageArea is a subclass of the class ImageArea, and is
  100.      * specified with an attribute of the form:
  101.      *         areaN=ImageAreaClassName,arguments...
  102.      * The ImageAreaClassName is parsed off and a new instance of that
  103.      * class is created.  The initializer for that class is passed a
  104.      * reference to the applet and the remainder of the attribute
  105.      * string, from which the class should retrieve any information it
  106.      * needs about the area it controls and the actions it needs to
  107.      * take within that area.
  108.      */
  109.     public void init() {
  110.     String s;
  111.  
  112.     parseHighlight(getParameter("highlight"));
  113.     baseImage = getImage(getDocumentBase(), getParameter("img"));
  114.     Vector areaVec = new Vector();
  115.     int num = 1;
  116.     while (true) {
  117.         ImageMapArea newArea;
  118.         s = getParameter("area"+num);
  119.         if (s == null) {
  120.         // Try rect for backwards compatibility.
  121.         s = getParameter("rect"+num);
  122.         if (s == null) {
  123.             break;
  124.         }
  125.         String url = getParameter("href"+num);
  126.         if (url != null)
  127.             s += "," + url;
  128.         newArea = new HrefArea();
  129.         } else {
  130.         int classend = s.indexOf(",");
  131.         try {
  132.             String name = s.substring(0, classend);
  133.             newArea = (ImageMapArea) Class.forName(name).newInstance();
  134.         } catch (Exception e) {
  135.             e.printStackTrace();
  136.             break;
  137.         }
  138.         s = s.substring(classend+1);
  139.         }
  140.         newArea.init(this, s);
  141.         areaVec.addElement(newArea);
  142.         num++;
  143.     }
  144.     areas = new ImageMapArea[areaVec.size()];
  145.     areaVec.copyInto(areas);
  146.     checkSize();
  147.     }
  148.  
  149.     /**
  150.      * Check the size of this applet while the image is being loaded.
  151.      */
  152.     synchronized void checkSize() {
  153.     int w = baseImage.getWidth(this);
  154.     int h = baseImage.getHeight(this);
  155.     if (w > 0 && h > 0) {
  156.         resize(w, h);
  157.         repaintrect.x = repaintrect.y = 0;
  158.         repaintrect.width = w;
  159.         repaintrect.height = h;
  160.         fullrepaint = true;
  161.         repaint();
  162.     }
  163.     }
  164.  
  165.     private boolean fullrepaint = false;
  166.     private Rectangle repaintrect = new Rectangle();
  167.     private long lastupdate = 0;
  168.     private final static long UPDATERATE = 100;
  169.  
  170.     /**
  171.      * Handle updates from images being loaded.
  172.      */
  173.     public boolean imageUpdate(Image img, int infoflags,
  174.                    int x, int y, int width, int height) {
  175.     if ((infoflags & (WIDTH | HEIGHT)) != 0) {
  176.         checkSize();
  177.     }
  178.     if ((infoflags & (SOMEBITS | FRAMEBITS | ALLBITS)) != 0) {
  179.         repaint(((infoflags & (FRAMEBITS | ALLBITS)) != 0)
  180.             ? 0 : UPDATERATE,
  181.             x, y, width, height);
  182.     }
  183.     return (infoflags & (ALLBITS | ERROR)) == 0;
  184.     }
  185.  
  186.     /**
  187.      * Paint the image and all active highlights.
  188.      */
  189.     public void paint(Graphics g) {
  190.     synchronized(this) {
  191.         if (fullrepaint) {
  192.         g = g.create();
  193.         g.clipRect(repaintrect.x, repaintrect.y,
  194.                repaintrect.width, repaintrect.height);
  195.         fullrepaint = false;
  196.         }
  197.     }
  198.     if (baseImage == null) {
  199.         return;
  200.     }
  201.     g.drawImage(baseImage, 0, 0, this);
  202.     if (areas != null) {
  203.         for (int i = areas.length; --i >= 0; ) {
  204.         if (areas[i].active || areas[i].entered) {
  205.             areas[i].setState(g, areas[i].entered);
  206.         }
  207.         }
  208.     }
  209.     }
  210.  
  211.     /**
  212.      * Update the active highlights on the image.
  213.      */
  214.     public void update(Graphics g) {
  215.     if (fullrepaint) {
  216.         paint(g);
  217.         return;
  218.     }
  219.     if (baseImage == null) {
  220.         return;
  221.     }
  222.     g.drawImage(baseImage, 0, 0, this);
  223.     if (areas == null) {
  224.         return;
  225.     }
  226.     // First unhighlight all of the deactivated areas
  227.     for (int i = areas.length; --i >= 0; ) {
  228.         if (areas[i].active && !areas[i].entered) {
  229.         areas[i].setState(g, false);
  230.         }
  231.     }
  232.     // Then highlight all of the activated areas
  233.     for (int i = areas.length; --i >= 0; ) {
  234.         if (areas[i].entered) {
  235.         areas[i].setState(g, true);
  236.         }
  237.     }
  238.     }
  239.  
  240.     /**
  241.      * Make sure that no ImageAreas are highlighted.
  242.      */
  243.     public void mouseExit() {
  244.     boolean changed = false;
  245.  
  246.     for (int i = 0; i < areas.length; i++) {
  247.         if (areas[i].active) {
  248.         areas[i].entered = false;
  249.         changed = true;
  250.         }
  251.     }
  252.     if (changed) {
  253.         repaint();
  254.     }
  255.     }
  256.  
  257.     /**
  258.      * Find the ImageAreas that the mouse is in.
  259.      */
  260.     public boolean mouseMove(java.awt.Event evt, int x, int y) {
  261.     boolean changed = false;
  262.     boolean propagate = true;
  263.  
  264.     for (int i = 0; i < areas.length; i++) {
  265.         if (areas[i].inside(x, y)) {
  266.         areas[i].entered = propagate;
  267.         if (areas[i].terminal) {
  268.             propagate = false;
  269.         }
  270.         } else {
  271.         areas[i].entered = false;
  272.         }
  273.  
  274.         if (areas[i].active != areas[i].entered) {
  275.         changed = true;
  276.         }
  277.     }
  278.  
  279.     if (changed) {
  280.         repaint();
  281.     }
  282.  
  283.     return true;
  284.     }
  285.  
  286.     int pressX;
  287.     int pressY;
  288.  
  289.     /**
  290.      * Inform all active ImageAreas of a mouse press.
  291.      */
  292.     public boolean mouseDown(java.awt.Event evt, int x, int y) {
  293.     pressX = x;
  294.     pressY = y;
  295.  
  296.     for (int i = 0; i < areas.length; i++) {
  297.         if (areas[i].inside(x, y)) {
  298.         areas[i].press(x, y);
  299.         if (areas[i].terminal) {
  300.             break;
  301.         }
  302.         }
  303.     }
  304.  
  305.     return true;
  306.     }
  307.  
  308.     /**
  309.      * Inform all active ImageAreas of a mouse release.
  310.      * Only those areas that were inside the original mouseDown()
  311.      * are informed of the mouseUp.
  312.      */
  313.     public boolean mouseUp(java.awt.Event evt, int x, int y) {
  314.     for (int i = 0; i < areas.length; i++) {
  315.         if (areas[i].inside(pressX, pressY)) {
  316.         areas[i].lift(x, y);
  317.         if (areas[i].terminal) {
  318.             break;
  319.         }
  320.         }
  321.     }
  322.  
  323.     return true;
  324.     }
  325.  
  326.     /**
  327.      * Inform all active ImageAreas of a mouse drag.
  328.      * Only those areas that were inside the original mouseDown()
  329.      * are informed of the mouseUp.
  330.      */
  331.     public boolean mouseDrag(java.awt.Event evt, int x, int y) {
  332.     mouseMove(evt, x, y);
  333.     for (int i = 0; i < areas.length; i++) {
  334.         if (areas[i].inside(pressX, pressY)) {
  335.         areas[i].drag(x, y);
  336.         if (areas[i].terminal) {
  337.             break;
  338.         }
  339.         }
  340.     }
  341.  
  342.     return true;
  343.     }
  344. }
  345.  
  346. /**
  347.  * The base ImageArea class.
  348.  * This class performs the basic functions that most ImageArea
  349.  * classes will need and delegates specific actions to the subclasses.
  350.  *
  351.  * @author     Jim Graham
  352.  * @version     %I%, %G%
  353.  */
  354. class ImageMapArea implements ImageObserver {
  355.     /** The applet parent that contains this ImageArea. */
  356.     ImageMap parent;
  357.     /** The X location of the area (if rectangular). */
  358.     int X;
  359.     /** The Y location of the area (if rectangular). */
  360.     int Y;
  361.     /** The size().width of the area (if rectangular). */
  362.     int W;
  363.     /** The size().height of the area (if rectangular). */
  364.     int H;
  365.     /**
  366.      * This flag indicates whether the user was in this area during the
  367.      * last scan of mouse locations.
  368.      */
  369.     boolean entered = false;
  370.     /** This flag indicates whether the area is currently highlighted. */
  371.     boolean active = false;
  372.     /**
  373.      * This flag indicates whether the area is terminal.  Terminal areas
  374.      * prevent any areas which are under them from being activated when
  375.      * the mouse is inside them.  Some areas may wish to change this to
  376.      * false so that they can augment other areas that they are on top of.
  377.      */
  378.     boolean terminal = true;
  379.     /**
  380.      * This is the default highlight image if no special effects are
  381.      * needed to draw the highlighted image.  It is created by the
  382.      * default "makeImages()" method.
  383.      */
  384.     Image hlImage;
  385.  
  386.     /**
  387.      * Initialize this ImageArea as called from the applet.
  388.      * If the subclass does not override this initializer, then it
  389.      * will perform the basic functions of setting the parent applet
  390.      * and parsing out 4 numbers from the argument string which specify
  391.      * a rectangular region for the ImageArea to act on.
  392.      * The remainder of the argument string is passed to the handleArg()
  393.      * method for more specific handling by the subclass.
  394.      */
  395.     public void init(ImageMap parent, String args) {
  396.     this.parent = parent;
  397.     StringTokenizer st = new StringTokenizer(args, ", ");
  398.     X = Integer.parseInt(st.nextToken());
  399.     Y = Integer.parseInt(st.nextToken());
  400.     W = Integer.parseInt(st.nextToken());
  401.     H = Integer.parseInt(st.nextToken());
  402.     if (st.hasMoreTokens()) {
  403.         // hasMoreTokens() Skips the trailing comma
  404.         handleArg(st.nextToken(""));
  405.     } else {
  406.         handleArg(null);
  407.     }
  408.     makeImages();
  409.     }
  410.  
  411.     /**
  412.      * This method handles the remainder of the argument string after
  413.      * the standard initializer has parsed off the 4 rectangular
  414.      * parameters.  If the subclass does not override this method,
  415.      * the remainder will be ignored.
  416.      */
  417.     public void handleArg(String s) {
  418.     }
  419.  
  420.     /**
  421.      * This method sets the image to be used to render the ImageArea
  422.      * when it is highlighted.
  423.      */
  424.     public void setHighlight(Image img) {
  425.     hlImage = img;
  426.     }
  427.  
  428.     /**
  429.      * This method handles the construction of the various images
  430.      * used to highlight this particular ImageArea when the user
  431.      * interacts with it.
  432.      */
  433.     public void makeImages() {
  434.     setHighlight(parent.getHighlight(X, Y, W, H));
  435.     }
  436.  
  437.     /**
  438.      * This method tests to see if a point is inside this ImageArea.
  439.      * The standard method assumes a rectangular area as parsed by
  440.      * the standard initializer.  If a more complex area is required
  441.      * then this method will have to be overridden by the subclass.
  442.      */
  443.     public boolean inside(int x, int y) {
  444.     return (x >= X && x < (X + W) && y >= Y && y < (Y + H));
  445.     }
  446.  
  447.     /**
  448.      * This utility method draws a rectangular subset of a highlight
  449.      * image.
  450.      */
  451.     public void drawImage(Graphics g, Image img, int imgx, int imgy,
  452.               int x, int y, int w, int h) {
  453.     Graphics ng = g.create();
  454.     ng.clipRect(x, y, w, h);
  455.     ng.drawImage(img, imgx, imgy, this);
  456.     }
  457.  
  458.     /**
  459.      * This method handles the updates from drawing the images.
  460.      */
  461.     public boolean imageUpdate(Image img, int infoflags,
  462.                    int x, int y, int width, int height) {
  463.     if (img == hlImage) {
  464.         return parent.imageUpdate(img, infoflags, x + X, y + Y,
  465.                       width, height);
  466.     } else {
  467.         return false;
  468.     }
  469.     }
  470.  
  471.     /**
  472.      * This utility method shows a string in the status bar.
  473.      */
  474.     public void showStatus(String msg) {
  475.     parent.getAppletContext().showStatus(msg);
  476.     }
  477.  
  478.     /**
  479.      * This utility method tells the browser to visit a URL.
  480.      */
  481.     public void showDocument(URL u) {
  482.     parent.getAppletContext().showDocument(u);
  483.     }
  484.  
  485.     /**
  486.      * This method highlights the specified area when the user enters
  487.      * it with his mouse.  The standard highlight method is to replace
  488.      * the indicated rectangular area of the image with the primary
  489.      * highlighted image.
  490.      */
  491.     public void highlight(Graphics g, boolean on) {
  492.     if (on) {
  493.         g.drawImage(hlImage, X, Y, this);
  494.     } else {
  495.         drawImage(g, parent.baseImage, 0, 0, X, Y, W, H);
  496.     }
  497.     }
  498.  
  499.     /**
  500.      * This method changes the active state of the ImageArea, which
  501.      * indicates whether the user is currently "inside" this area.
  502.      * It turns around and calls the highlight method which is likely
  503.      * to have been overridden by subclasses seeking a custom highlight.
  504.      */
  505.     public void setState(Graphics g, boolean on) {
  506.     highlight(g, on);
  507.     active = on;
  508.     }
  509.  
  510.     /**
  511.      * The press method is called when the user presses the mouse
  512.      * button inside the ImageArea.  The location is supplied, but
  513.      * the standard implementation is to call the overloaded method
  514.      * with no arguments.
  515.      */
  516.     public void press(int x, int y) {
  517.     press();
  518.     }
  519.  
  520.     /**
  521.      * The overloaded press method is called when the user presses the
  522.      * mouse button inside the ImageArea.  This method can be overridden
  523.      * if the ImageArea does not need to know the location of the press.
  524.      */
  525.     public void press() {
  526.     }
  527.  
  528.     /**
  529.      * The lift method is called when the user releases the mouse button.
  530.      * The location is supplied, but the standard implementation is to
  531.      * call the overloaded method with no arguments.  Only those ImageAreas
  532.      * that were informed of a press will be informed of the corresponding
  533.      * release.
  534.      */
  535.     public void lift(int x, int y) {
  536.     lift();
  537.     }
  538.  
  539.     /**
  540.      * The overloaded lift method is called when the user releases the
  541.      * mouse button.  This method can be overridden if the ImageArea
  542.      * does not need to know the location of the release.
  543.      */
  544.     public void lift() {
  545.     }
  546.  
  547.     /**
  548.      * The drag method is called when the user moves the mouse while
  549.      * the button is pressed.  Only those ImageAreas that were informed
  550.      * of a press will be informed of the corresponding mouse movements.
  551.      */
  552.     public void drag(int x, int y) {
  553.     }
  554. }
  555.  
  556. /**
  557.  * The classic "Fetch a URL" ImageArea class.
  558.  * This class extends the basic ImageArea Class to fetch a URL when
  559.  * the user clicks in the area.
  560.  *
  561.  * @author     Jim Graham
  562.  * @version     %I%, %G%
  563.  */
  564. class HrefArea extends ImageMapArea {
  565.     /** The URL to be fetched when the user clicks on this area. */
  566.     URL anchor;
  567.  
  568.     /**
  569.      * The argument string is the URL to be fetched.
  570.      */
  571.     public void handleArg(String arg) {
  572.     try {
  573.         anchor = new URL(parent.getDocumentBase(), arg);
  574.     } catch (MalformedURLException e) {
  575.         anchor = null;
  576.     }
  577.     }
  578.  
  579.     /**
  580.      * The status message area is updated to show the destination URL.
  581.      * The default graphics highlight feedback is used.
  582.      */
  583.     public void highlight(Graphics g, boolean on) {
  584.     super.highlight(g, on);
  585.     showStatus((on && anchor != null)
  586.            ? "Go To " + anchor.toExternalForm()
  587.            : null);
  588.     }
  589.  
  590.     /**
  591.      * The new URL is fetched when the user releases the mouse button
  592.      * only if they are still in the area.
  593.      */
  594.     public void lift(int x, int y) {
  595.     if (inside(x, y) && anchor != null) {
  596.         showDocument(anchor);
  597.     }
  598.     // Note that we should not be active, so no repaint is necessary.
  599.     }
  600. }
  601.  
  602. /**
  603.  * An audio feedback ImageArea class.
  604.  * This class extends the basic ImageArea Class to play a sound each
  605.  * time the user enters the area.
  606.  *
  607.  * @author     Jim Graham
  608.  * @version     %I%, %G%
  609.  */
  610. class SoundArea extends ImageMapArea {
  611.     /** The URL of the sound to be played. */
  612.     String sound;
  613.  
  614.     /**
  615.      * The argument is the URL of the sound to be played.
  616.      * This method also sets this type of area to be non-terminal.
  617.      */
  618.     public void handleArg(String arg) {
  619.     sound = arg;
  620.     terminal = false;
  621.     }
  622.  
  623.     /**
  624.      * The highlight method plays the sound in addition to the usual
  625.      * graphical highlight feedback.
  626.      */
  627.     public void highlight(Graphics g, boolean on) {
  628.     if (on && !active) {
  629.         parent.play(parent.getDocumentBase(), sound);
  630.     }
  631.     super.highlight(g, on);
  632.     }
  633. }
  634.  
  635. /**
  636.  * An click feedback ImageArea class.
  637.  * This class extends the basic ImageArea Class to show the locations
  638.  * of clicks in the image in the status message area.  This utility
  639.  * ImageArea class is useful when setting up ImageMaps.
  640.  *
  641.  * @author     Jim Graham
  642.  * @version     %I%, %G%
  643.  */
  644. class ClickArea extends ImageMapArea {
  645.     /** The X location of the last mouse press. */
  646.     int startx;
  647.     /** The Y location of the last mouse press. */
  648.     int starty;
  649.  
  650.     /**
  651.      * The argument is ignored, but we use this method to set this type
  652.      * of area to be non-terminal.
  653.      */
  654.     public void handleArg(String arg) {
  655.     terminal = false;
  656.     }
  657.  
  658.     /** This class overrides the highlight method to prevent highlighting. */
  659.     public void highlight(Graphics g, boolean on) {
  660.     }
  661.  
  662.     String ptstr(int x, int y) {
  663.     return "("+x+", "+y+")";
  664.     }
  665.  
  666.     /**
  667.      * When the user presses the mouse button, start showing coordinate
  668.      * feedback in the status message line.
  669.      */
  670.     public void press(int x, int y) {
  671.     showStatus("Clicked at "+ptstr(x, y));
  672.     startx = x;
  673.     starty = y;
  674.     }
  675.  
  676.     /**
  677.      * Update the coordinate feedback every time the user moves the mouse
  678.      * while he has the button pressed.
  679.      */
  680.     public void drag(int x, int y) {
  681.     showStatus("Rectangle from "+ptstr(startx, starty)
  682.            +" to "+ptstr(x, y)
  683.            +" is "+(x-startx)+"x"+(y-starty));
  684.     }
  685.  
  686.     /**
  687.      * Update the coordinate feedback one last time when the user releases
  688.      * the mouse button.
  689.      */
  690.     public void lift(int x, int y) {
  691.     drag(x, y);
  692.     }
  693. }
  694.  
  695. /**
  696.  * A message feedback ImageArea class.
  697.  * This class extends the basic ImageArea Class to show the a given
  698.  * message in the status message area when the user enters this area.
  699.  *
  700.  * @author     Jim Graham
  701.  * @version     %I%, %G%
  702.  */
  703. class NameArea extends ImageMapArea {
  704.     /** The string to be shown in the status message area. */
  705.     String name;
  706.  
  707.     /**
  708.      * The argument is the string to be displayed in the status message
  709.      * area.  This method also sets this type of area to be non-terminal.
  710.      */
  711.     public void handleArg(String arg) {
  712.     name = arg;
  713.     terminal = false;
  714.     }
  715.  
  716.     /**
  717.      * The highlight method displays the message in addition to the usual
  718.      * graphical highlight feedback.
  719.      */
  720.     public void highlight(Graphics g, boolean on) {
  721.     super.highlight(g, on);
  722.     showStatus(on ? name : null);
  723.     }
  724.  
  725. }
  726.  
  727. /**
  728.  * An improved "Fetch a URL" ImageArea class.
  729.  * This class extends the basic ImageArea Class to fetch a URL when
  730.  * the user clicks in the area.  In addition, special custom highlights
  731.  * are used to make the area look and feel like a 3-D button.
  732.  *
  733.  * @author     Jim Graham
  734.  * @version     %I%, %G%
  735.  */
  736. class HrefButtonArea extends ImageMapArea {
  737.     /** The URL to be fetched when the user clicks on this area. */
  738.     URL anchor;
  739.     /** The highlight image for when the button is "UP". */
  740.     Image upImage;
  741.     /** The highlight image for when the button is "DOWN". */
  742.     Image downImage;
  743.     /** This flag indicates if the "button" is currently pressed. */
  744.     boolean pressed = false;
  745.     /** The border size for the 3-D effect. */
  746.     int border = 5;
  747.  
  748.     /**
  749.      * The argument string is the URL to be fetched.
  750.      * This method also constructs the various highlight images needed
  751.      * to achieve the 3-D effect.
  752.      */
  753.     public void handleArg(String arg) {
  754.     try {
  755.         anchor = new URL(parent.getDocumentBase(), arg);
  756.     } catch (MalformedURLException e) {
  757.         anchor = null;
  758.     }
  759.     if (border * 2 > W || border * 2 > H) {
  760.         border = Math.min(W, H) / 2;
  761.     }
  762.     }
  763.  
  764.     public void makeImages() {
  765.     upImage = parent.getHighlight(X, Y, W, H,
  766.                       new ButtonFilter(false,
  767.                                parent.hlpercent,
  768.                                border, W, H));
  769.     downImage = parent.getHighlight(X, Y, W, H,
  770.                     new ButtonFilter(true,
  771.                              parent.hlpercent,
  772.                              border, W, H));
  773.     }
  774.  
  775.     public boolean imageUpdate(Image img, int infoflags,
  776.                    int x, int y, int width, int height) {
  777.     if (img == (pressed ? downImage : upImage)) {
  778.         return parent.imageUpdate(img, infoflags, x + X, y + Y,
  779.                       width, height);
  780.     } else {
  781.         return (img == downImage || img == upImage);
  782.     }
  783.     }
  784.  
  785.     /**
  786.      * The status message area is updated to show the destination URL.
  787.      * The graphical highlight is achieved using the ButtonFilter.
  788.      */
  789.     public void highlight(Graphics g, boolean on) {
  790.     if (on) {
  791.         setHighlight(pressed ? downImage : upImage);
  792.     }
  793.     super.highlight(g, on);
  794.     showStatus((on && anchor != null)
  795.            ? "Go To " + anchor.toExternalForm()
  796.            : null);
  797.     }
  798.  
  799.     /**
  800.      * Since the highlight changes when the button is pressed, we need
  801.      * to record the "pressed" state and induce a repaint.
  802.      */
  803.     public void press() {
  804.     parent.repaint();
  805.     pressed = true;
  806.     }
  807.  
  808.     /**
  809.      * The new URL is fetched when the user releases the mouse button
  810.      * only if they are still in the area.
  811.      */
  812.     public void lift(int x, int y) {
  813.     pressed = false;
  814.     parent.repaint();
  815.     if (inside(x, y) && anchor != null) {
  816.         showDocument(anchor);
  817.     }
  818.     }
  819. }
  820.  
  821. /**
  822.  * An improved, round, "Fetch a URL" ImageArea class.
  823.  * This class extends the HrefButtonArea Class to make the 3D button
  824.  * a rounded ellipse.  All of the same feedback and operational
  825.  * charactistics as the HrefButtonArea apply.
  826.  *
  827.  * @author     Jim Graham
  828.  * @version     %I%, %G%
  829.  */
  830. class RoundHrefButtonArea extends HrefButtonArea {
  831.     public void makeImages() {
  832.     upImage = parent.getHighlight(X, Y, W, H,
  833.                       new RoundButtonFilter(false,
  834.                                 parent.hlpercent,
  835.                                 border, W, H));
  836.     downImage = parent.getHighlight(X, Y, W, H,
  837.                     new RoundButtonFilter(true,
  838.                                   parent.hlpercent,
  839.                                   border, W, H));
  840.     }
  841. }
  842.  
  843. class HighlightFilter extends RGBImageFilter {
  844.     boolean brighter;
  845.     int percent;
  846.  
  847.     public HighlightFilter(boolean b, int p) {
  848.     brighter = b;
  849.     percent = p;
  850.     canFilterIndexColorModel = true;
  851.     }
  852.  
  853.     public int filterRGB(int x, int y, int rgb) {
  854.     int r = (rgb >> 16) & 0xff;
  855.     int g = (rgb >> 8) & 0xff;
  856.     int b = (rgb >> 0) & 0xff;
  857.     if (brighter) {
  858.         r = (255 - ((255 - r) * (100 - percent) / 100));
  859.         g = (255 - ((255 - g) * (100 - percent) / 100));
  860.         b = (255 - ((255 - b) * (100 - percent) / 100));
  861.     } else {
  862.         r = (r * (100 - percent) / 100);
  863.         g = (g * (100 - percent) / 100);
  864.         b = (b * (100 - percent) / 100);
  865.     }
  866.     if (r < 0) r = 0;
  867.     if (r > 255) r = 255;
  868.     if (g < 0) g = 0;
  869.     if (g > 255) g = 255;
  870.     if (b < 0) b = 0;
  871.     if (b > 255) b = 255;
  872.     return (rgb & 0xff000000) | (r << 16) | (g << 8) | (b << 0);
  873.     }
  874. }
  875.  
  876. class ButtonFilter extends RGBImageFilter {
  877.     boolean pressed;
  878.     int defpercent;
  879.     int border;
  880.     int width;
  881.     int height;
  882.  
  883.     ColorModel models[] = new ColorModel[7];
  884.     ColorModel origbuttonmodel;
  885.  
  886.     public ButtonFilter(boolean press, int p, int b, int w, int h) {
  887.     pressed = press;
  888.     defpercent = p;
  889.     border = b;
  890.     width = w;
  891.     height = h;
  892.     }
  893.  
  894.     public void setHints(int hints) {
  895.     super.setHints(hints & (~ImageConsumer.COMPLETESCANLINES));
  896.     }
  897.  
  898.     public void setColorModel(ColorModel model) {
  899.     if (model instanceof IndexColorModel && true) {
  900.         IndexColorModel icm = (IndexColorModel) model;
  901.         models[0] = filterIndexColorModel(icm, false, false, 0);
  902.         models[1] = filterIndexColorModel(icm, true, !pressed, defpercent);
  903.         models[2] = null;
  904.         if (pressed) {
  905.         models[3] = filterIndexColorModel(icm, true, false,
  906.                           defpercent/2);
  907.         } else {
  908.         models[3] = models[0];
  909.         }
  910.         models[4] = null;
  911.         models[5] = filterIndexColorModel(icm, true, pressed, defpercent);
  912.         models[6] = models[0];
  913.         origbuttonmodel = model;
  914.         consumer.setColorModel(models[3]);
  915.     } else {
  916.         super.setColorModel(model);
  917.     }
  918.     }
  919.  
  920.     public IndexColorModel filterIndexColorModel(IndexColorModel icm,
  921.                          boolean opaque,
  922.                          boolean brighter,
  923.                          int percent) {
  924.     byte r[] = new byte[256];
  925.     byte g[] = new byte[256];
  926.     byte b[] = new byte[256];
  927.     byte a[] = new byte[256];
  928.     int mapsize = icm.getMapSize();
  929.     icm.getReds(r);
  930.     icm.getGreens(g);
  931.     icm.getBlues(b);
  932.     if (opaque) {
  933.         icm.getAlphas(a);
  934.         for (int i = 0; i < mapsize; i++) {
  935.         int rgb = filterRGB(icm.getRGB(i), brighter, percent);
  936.         a[i] = (byte) (rgb >> 24);
  937.         r[i] = (byte) (rgb >> 16);
  938.         g[i] = (byte) (rgb >> 8);
  939.         b[i] = (byte) (rgb >> 0);
  940.         }
  941.     }
  942.     return new IndexColorModel(icm.getPixelSize(), mapsize, r, g, b, a);
  943.     }
  944.  
  945.     /**
  946.      * Define the ranges of varying highlight for the button.
  947.      * ranges is an array of 8 values which split up a scanline into
  948.      * 7 different regions of highlighting effect:
  949.      *
  950.      * ranges[0-1] = area outside of left edge of button
  951.      * ranges[1-2] = area inside UpperLeft highlight region left of center
  952.      * ranges[2-3] = area requiring custom highlighting left of center
  953.      * ranges[3-4] = area inside center of button
  954.      * ranges[4-5] = area requiring custom highlighting right of center
  955.      * ranges[5-6] = area inside LowerRight highlight region right of center
  956.      * ranges[6-7] = area outside of right edge of button
  957.      *
  958.      * Note that ranges[0-1] and ranges[6-7] are empty where the edges of
  959.      * the button touch the left and right edges of the image (everywhere
  960.      * on a square button) and ranges[2-3] and ranges[4-5] are only nonempty
  961.      * in those regions where the UpperLeft highlighting has leaked over
  962.      * the "top" of the button onto parts of its right edge or where the
  963.      * LowerRight highlighting has leaked under the "bottom" of the button
  964.      * onto parts of its left edge (can't happen on square buttons, happens
  965.      * occasionally on round buttons).
  966.      */
  967.     public void buttonRanges(int y, int ranges[]) {
  968.     ranges[0] = ranges[1] = 0;
  969.     if (y < border) {
  970.         ranges[2] = ranges[3] = ranges[4] = ranges[5] = width - y;
  971.     } else if (y > height - border) {
  972.         ranges[2] = ranges[3] = ranges[4] = ranges[5] = height - y;
  973.     } else {
  974.         ranges[2] = ranges[3] = border;
  975.         ranges[4] = ranges[5] = width - border;
  976.     }
  977.     ranges[6] = ranges[7] = width;
  978.     }
  979.  
  980.     public void setPixels(int x, int y, int w, int h,
  981.               ColorModel model, byte pixels[], int off,
  982.               int scansize) {
  983.     if (model == origbuttonmodel) {
  984.         int ranges[] = new int[8];
  985.         int x2 = x + w;
  986.         for (int cy = y; cy < y + h; cy++) {
  987.         buttonRanges(cy, ranges);
  988.         for (int i = 0; i < 7; i++) {
  989.             if (x2 > ranges[i] && x < ranges[i+1]) {
  990.             int cx1 = Math.max(x, ranges[i]);
  991.             int cx2 = Math.min(x2, ranges[i+1]);
  992.             if (models[i] == null) {
  993.                 super.setPixels(cx1, cy, cx2 - cx1, 1,
  994.                         model, pixels,
  995.                         off + (cx1 - x), scansize);
  996.             } else {
  997.                 if (cx1 < cx2) {
  998.                 consumer.setPixels(cx1, cy, cx2 - cx1, 1,
  999.                            models[i], pixels,
  1000.                            off + (cx1 - x), scansize);
  1001.                 }
  1002.             }
  1003.             }
  1004.         }
  1005.         off += scansize;
  1006.         }
  1007.     } else {
  1008.         super.setPixels(x, y, w, h, model, pixels, off, scansize);
  1009.     }
  1010.     }
  1011.  
  1012.     public int filterRGB(int x, int y, int rgb) {
  1013.     boolean brighter;
  1014.     int percent;
  1015.     if ((x < border && y < height - x) || (y < border && x < width - y)) {
  1016.         brighter = !pressed;
  1017.         percent = defpercent;
  1018.     } else if (x >= width - border || y >= height - border) {
  1019.         brighter = pressed;
  1020.         percent = defpercent;
  1021.     } else if (pressed) {
  1022.         brighter = false;
  1023.         percent = defpercent / 2;
  1024.     } else {
  1025.         return rgb & 0x00ffffff;
  1026.     }
  1027.     return filterRGB(rgb, brighter, percent);
  1028.     }
  1029.  
  1030.     public int filterRGB(int rgb, boolean brighter, int percent) {
  1031.     int r = (rgb >> 16) & 0xff;
  1032.     int g = (rgb >> 8) & 0xff;
  1033.     int b = (rgb >> 0) & 0xff;
  1034.     if (brighter) {
  1035.         r = (255 - ((255 - r) * (100 - percent) / 100));
  1036.         g = (255 - ((255 - g) * (100 - percent) / 100));
  1037.         b = (255 - ((255 - b) * (100 - percent) / 100));
  1038.     } else {
  1039.         r = (r * (100 - percent) / 100);
  1040.         g = (g * (100 - percent) / 100);
  1041.         b = (b * (100 - percent) / 100);
  1042.     }
  1043.     if (r < 0) r = 0;
  1044.     if (g < 0) g = 0;
  1045.     if (b < 0) b = 0;
  1046.     if (r > 255) r = 255;
  1047.     if (g > 255) g = 255;
  1048.     if (b > 255) b = 255;
  1049.     return (rgb & 0xff000000) | (r << 16) | (g << 8) | (b << 0);
  1050.     }
  1051. }
  1052.  
  1053. class RoundButtonFilter extends ButtonFilter {
  1054.     int Xcenter;
  1055.     int Ycenter;
  1056.     int Yradsq;
  1057.     int innerW;
  1058.     int innerH;
  1059.     int Yrad2sq;
  1060.  
  1061.     public RoundButtonFilter(boolean press, int p, int b, int w, int h) {
  1062.     super(press, p, b, w, h);
  1063.     Xcenter = w/2;
  1064.     Ycenter = h/2;
  1065.     Yradsq = h * h / 4;
  1066.     innerW = w - border * 2;
  1067.     innerH = h - border * 2;
  1068.     Yrad2sq = innerH * innerH / 4;
  1069.     }
  1070.  
  1071.     public void buttonRanges(int y, int ranges[]) {
  1072.     int yrel = Math.abs(Ycenter - y);
  1073.     int xrel = (int) (Math.sqrt(Yradsq - yrel * yrel) * width / height);
  1074.     int xslash = width - (y * width / height);
  1075.     ranges[0] = 0;
  1076.     ranges[1] = Xcenter - xrel;
  1077.     ranges[6] = Xcenter + xrel;
  1078.     ranges[7] = width;
  1079.     if (y < border) {
  1080.         ranges[2] = ranges[3] = ranges[4] = Xcenter;
  1081.         ranges[5] = ranges[6];
  1082.     } else if (y + border >= height) {
  1083.         ranges[2] = ranges[1];
  1084.         ranges[3] = ranges[4] = ranges[5] = Xcenter;
  1085.     } else {
  1086.         int xrel2 = (int) (Math.sqrt(Yrad2sq - yrel * yrel)
  1087.                    * innerW / innerH);
  1088.         ranges[3] = Xcenter - xrel2;
  1089.         ranges[4] = Xcenter + xrel2;
  1090.         if (y < Ycenter) {
  1091.         ranges[2] = ranges[3];
  1092.         ranges[5] = ranges[6];
  1093.         } else {
  1094.         ranges[2] = ranges[1];
  1095.         ranges[5] = ranges[4];
  1096.         }
  1097.     }
  1098.     }
  1099.  
  1100.     private int savedranges[];
  1101.     private int savedy;
  1102.  
  1103.     private synchronized int[] getRanges(int y) {
  1104.     if (savedranges == null || savedy != y) {
  1105.         if (savedranges == null) {
  1106.         savedranges = new int[8];
  1107.         }
  1108.         buttonRanges(y, savedranges);
  1109.         savedy = y;
  1110.     }
  1111.     return savedranges;
  1112.     }
  1113.  
  1114.     public int filterRGB(int x, int y, int rgb) {
  1115.     boolean brighter;
  1116.     int percent;
  1117.     int i;
  1118.     int ranges[] = getRanges(y);
  1119.     for (i = 0; i < 7; i++) {
  1120.         if (x >= ranges[i] && x < ranges[i+1]) {
  1121.         break;
  1122.         }
  1123.     }
  1124.     double angle;
  1125.     switch (i) {
  1126.     default:
  1127.     case 0:
  1128.     case 6:
  1129.         return rgb & 0x00ffffff;
  1130.     case 1:
  1131.         brighter = !pressed;
  1132.         percent = defpercent;
  1133.         break;
  1134.     case 5:
  1135.         brighter = pressed;
  1136.         percent = defpercent;
  1137.         break;
  1138.     case 2:
  1139.         angle = Math.atan2(y - Ycenter, Xcenter - x);
  1140.         percent = defpercent - ((int) (Math.cos(angle) * 2 * defpercent));
  1141.         if (!pressed) {
  1142.         percent = -percent;
  1143.         }
  1144.         if (percent == 0) {
  1145.         return rgb;
  1146.         } else if (percent < 0) {
  1147.         percent = -percent;
  1148.         brighter = false;
  1149.         } else {
  1150.         brighter = true;
  1151.         }
  1152.         break;
  1153.     case 4:
  1154.         angle = Math.atan2(Ycenter - y, x - Xcenter);
  1155.         percent = defpercent - ((int) (Math.cos(angle) * 2 * defpercent));
  1156.         if (pressed) {
  1157.         percent = -percent;
  1158.         }
  1159.         if (percent == 0) {
  1160.         return rgb;
  1161.         } else if (percent < 0) {
  1162.         percent = -percent;
  1163.         brighter = false;
  1164.         } else {
  1165.         brighter = true;
  1166.         }
  1167.         break;
  1168.     case 3:
  1169.         if (!pressed) {
  1170.         return rgb & 0x00ffffff;
  1171.         }
  1172.         brighter = false;
  1173.         percent = defpercent;
  1174.         break;
  1175.     }
  1176.     return filterRGB(rgb, brighter, percent);
  1177.     }
  1178. }
  1179.